package baseFx.tests.common.context.spring;

import baseFx.common.context.spring.BeanScopes;
import baseFx.common.context.spring.SpringApplicationContext;
import baseFx.common.Var;
import baseFx.common.concurrent.Task;
import baseFx.common.utils.RandomUtils;
import baseFx.tests.common.context.spring.scan.Model5;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;

import java.util.concurrent.CountDownLatch;

public class SpringApplicationContextTest {
    @Test
    public void context生命周期的bean在不同的容器中相当于单例() {
        SpringApplicationContext context = new SpringApplicationContext();
        context.registerBean(Model.class, def -> {
            def.setScope(BeanScopes.SCOPE_CONTEXT);
        });
        context.refresh();

        Model m1 = context.getBean(Model.class);
        Model m2 = context.getBean(Model.class);

        SpringApplicationContext context2 = context.sub();
        Model m3 = context2.getBean(Model.class);
        Model m4 = context2.getBean(Model.class);

        Assert.assertEquals(m1, m2);
        Assert.assertEquals(m3, m4);
        Assert.assertNotEquals(m1, m3);
    }

    @Test
    public void 改变bean的默认生命周期() {
        SpringApplicationContext context = new SpringApplicationContext();
        context.addDefaultScope(Model.class.getPackage().getName() + ".*", BeanScopes.SCOPE_CONTEXT);
        context.registerBean(Model.class);
        context.refresh();
        context.registerBean(Model2.class);

        String[] names = context.getBeanNamesForType(Model.class);
        for (String name : names) {
            BeanDefinition def = context.getBeanDefinition(name);
            Assert.assertEquals(def.getScope(), BeanScopes.SCOPE_CONTEXT);
        }

        String[] names2 = context.getBeanNamesForType(Model2.class);
        for (String name : names2) {
            BeanDefinition def = context.getBeanDefinition(name);
            Assert.assertEquals(def.getScope(), BeanScopes.SCOPE_CONTEXT);
        }
        Assert.assertTrue(names.length > 0);
    }

    @Test
    public void 带命名的Bean() {
        SpringApplicationContext context = new SpringApplicationContext();
        context.registerBean(ModelBean.class);
        context.refresh();
        Model3 m3 = new Model3();
        context.getAutowireCapableBeanFactory().autowireBean(m3);

        Assert.assertEquals(m3.getModel2().id, 2);
        Assert.assertEquals(m3.getModel2().getModel().id, "2");
    }

    @Test
    public void 扫描指定的包() {
        SpringApplicationContext context = new SpringApplicationContext();
        context.addDefaultScope(Model5.class.getCanonicalName(), BeanScopes.SCOPE_PROTOTYPE);
        context.refresh();
        context.scanPackage(s -> {
            s.addIncludeFilter((r, f) -> {
                return r.getClassMetadata().getClassName().equals(Model5.class.getCanonicalName());
            });
        }, Model5.class.getPackage().getName());

        Model5 m1 = context.getBean(Model5.class);
        Model5 m2 = context.getBean(Model5.class);
        Assert.assertNotEquals(m1, m2);
    }

    @Test
    public void 外部创建的bean不会在容器关闭时释放() {
        SpringApplicationContext context = new SpringApplicationContext();
        context.registerBean(CloseModel.class);
        context.refresh();
        CloseModel m1 = context.getBean(CloseModel.class);
        context.close();
        Assert.assertTrue(m1.closed());

        CloseModel cm = new CloseModel();
        SpringApplicationContext context2 = new SpringApplicationContext();
        context2.register(def -> def.bean = cm);
        context2.refresh();
        CloseModel m2 = context2.getBean(CloseModel.class);
        context2.close();
        Assert.assertEquals(cm, m2);
        Assert.assertFalse(m2.closed());
    }

    @Test
    public void getBeanNamesForType() {
        SpringApplicationContext context = new SpringApplicationContext();
        context.registerBean(Model.class);
        context.refresh();

        Model m1 = context.getBean(Model.class);
        Model m2 = context.getBean(Model.class);

        SpringApplicationContext ctx = context.sub();
        Model m3 = ctx.getBean(Model.class);

        Assert.assertEquals(m1, m2);
        Assert.assertEquals(m1, m3);

        String[] names = context.getBeanNamesForType(Model.class);
        String[] ctxNames = ctx.getBeanNamesForType(Model.class);

        Assert.assertEquals(names.length, ctxNames.length);
    }

    @Test
    public void 容器包含自身() {
        SpringApplicationContext context = new SpringApplicationContext();
        context.registerBean(ModelBean.class);
        context.refresh();
        SpringApplicationContext context2 = context.sub();
        Model4 model = context2.getBean(Model4.class);
        Assert.assertNotEquals(model.getContext(), context);
        Assert.assertEquals(model.getContext(), context2);
    }

    @Test
    @Ignore
    public void 并发创建子容器() throws InterruptedException {
        SpringApplicationContext context = new SpringApplicationContext();
        context.refresh();
        CountDownLatch latch = new CountDownLatch(50);
        Var<Boolean> isEx = new Var<>(false);
        for (int i = 0; i < 50; i++) {
            Task.run(s -> {
                for (int j = 0; j < 100; j++) {
                    try (SpringApplicationContext c = context.sub()) {
                        Task.delay(Long.parseLong(RandomUtils.numByRange(5, 200)));
                    }
                }
            }, i).onCompleted(t -> {
                if (t.getError() != null) {
                    isEx.set(true);
                }
                latch.countDown();
            });
        }
        latch.await();
        Assert.assertFalse(isEx.get());
    }

    @Test
    public void 原型bean也会被释放() {
        SpringApplicationContext context = new SpringApplicationContext();
        context.addDefaultScope(CloseModel.class.getCanonicalName(), BeanScopes.SCOPE_PROTOTYPE);
        context.registerBean(CloseModel.class);
        context.refresh();

        CloseModel m1 = context.getBean(CloseModel.class);
        CloseModel m2 = context.getBean(CloseModel.class);
        Assert.assertNotEquals(m1, m2);
        Assert.assertFalse(m1.closed());
        Assert.assertFalse(m2.closed());
        context.close();
        Assert.assertTrue(m1.closed());
        Assert.assertTrue(m2.closed());
    }
}
